동적 계획법 (Dynamic Programming)
2020-10-22
동적 계획법
부분 문제 반복과 최적 구조를 가지고 있는 문제를 간단한 여러 개의 문제로 나누어 푸는 방법을 말한다. 부분 문제 반복과 최적 부분 구조의 의미는 다음과 같다.
- 부분 문제 반복: 부분 문제들의 답이 바뀌지 않고 계속 반복되는 것
- 최적 부분 구조: 큰 문제의 최적 솔루션에 작은 문제의 최적 솔루션이 포함되는 것
처음 봤을 땐 도대체 무슨 말인지 이해할 수 가 없다. 쉽게 말하면 기존 값을 재활용해서 풀 수 있는 문제는 기존 값을 재활용해서 풀겠다는 말이다. 대표적인 문제로 피보나치 수열이 있다. 해당 문제를 통해 살펴보자
피보나치 수열을 구하는 함수는 다음과 같이 작성할 수 있다.
int fib(int n) {
if(n == 0) {
return 0;
}else if() {
return 1;
} else {
return fib(n - 1) + fib(n - 2);
}
}
이때, fib(5)를 구하려고하면 다음과 같은 계산과정이 일어난다.
- fib(5)
- fib(4) + fib(3)
- (fib(3) + fib(2)) + (fib(2) + fib(1))
- ((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
- (((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
위 계산과정을 살펴보면 fib(3), fib(2), fib(1), fib(0)의 경우 계속 중복되어 계산되고 이는 전체적인 계산 속도를 떨어뜨린다. 이를 방지하기위해 각 함수의 계산 값을 저장해뒀다가 필요할 때 그 값을 꺼내서 쓰도록 하자. 함수의 실행 값을 저장는 배열 dp를 추가하면 다음과 같이 함수를 수정할 수 있다.
int[] dp = new int[n + 1];
int fib(int n) {
if(n == 0) {
return 0;
}else if(n == 1) {
return 1;
}else if(dp[n] == 0) { // 해당 피보나치 수열 값이 아직 연산되지 않았을 경우
dp[n] = fib(n - 1) + fib(n - 2);
return dp[n];
}else {
return dp[n];
}
}
이렇게 각 계산 값을 저장하면, 중복계산이 줄어들고 시간 복잡도는 O(n)이 된다.